﻿namespace JoinBox.MathEx;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

#if CAD
using IFoxCAD.Basal;
#endif

[Serializable]
[StructLayout(LayoutKind.Sequential)]
[DebuggerDisplay("{DebuggerDisplay,nq}")]
[DebuggerTypeProxy(typeof(Rect))]
public class Rect : IEquatable<Rect>
{
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string DebuggerDisplay => ToString("f4");

    #region 字段
    // 这里的成员不要用{get}封装成属性,否则会导致跳转了一次函数,
    // 10w图元将会从187毫秒变成400毫秒
    // 不用 protected 否则子类传入Rect对象进来无法用
    internal double _X;
    internal double _Y;
    internal double _Right;
    internal double _Top;
    #endregion

    #region 成员
    public double X => _X;
    public double Y => _Y;
    public double Left => _X;
    public double Bottom => _Y;
    public double Right => _Right;
    public double Top => _Top;

    public double Width => _Right - _X;
    public double Height => _Top - _Y;
    public double Area
    {
        get
        {
            var ar = (_Right - _X) * (_Top - _Y);
            return ar < 1e-10 ? 0 : ar;
        }
    }

    public PointV MinPoint => LeftLower;
    public PointV MaxPoint => RightUpper;
    public PointV CenterPoint => Midst;

    /// <summary>
    /// 左下Min
    /// </summary>
    public PointV LeftLower => new(_X, _Y);

    /// <summary>
    /// 左中
    /// </summary>
    public PointV LeftMidst => new(_X, Midst.Y);

    /// <summary>
    /// 左上
    /// </summary>
    public PointV LeftUpper => new(_X, _Top);

    /// <summary>
    /// 右上Max
    /// </summary>
    public PointV RightUpper => new(_Right, _Top);

    /// <summary>
    /// 右中
    /// </summary>
    public PointV RightMidst => new(_Right, Midst.Y);

    /// <summary>
    /// 右下
    /// </summary>
    public PointV RightBottom => new(_Right, _Y);

    /// <summary>
    /// 中间
    /// </summary>
    public PointV Midst => new(((_Right - _X) * 0.5) + _X, ((_Top - _Y) * 0.5) + _Y);

    /// <summary>
    /// 中上
    /// </summary>
    public PointV MidstUpper => new(Midst.X, _Top);

    /// <summary>
    /// 中下
    /// </summary>
    public PointV MidstBottom => new(Midst.X, _Y);

    /// <summary>
    /// 是一个点
    /// </summary>
    /// 面积是0不一定是点,所以需要这样判断,
    /// 因为可能是水平或者垂直的直线,没有斜率的时候是包围盒面积是0
    public bool IsPoint => Math.Abs(_X - _Right) < 1e-10 && Math.Abs(_Y - _Top) < 1e-10;
    #endregion

    #region 构造
    public Rect() { }

    public Rect(double left, double bottom, double right, double top)
    {
        _X = left;
        _Y = bottom;
        _Right = right;
        _Top = top;
    }

    /// <summary>
    /// 构造矩形类
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p3"></param>
    /// <param name="check">是否检查大小</param>
    public Rect(PointV p1, PointV p3, bool check = false)
    {
        if (check)
        {
            _X = Math.Min(p1.X, p3.X);
            _Y = Math.Min(p1.Y, p3.Y);
            _Right = Math.Max(p1.X, p3.X);
            _Top = Math.Max(p1.Y, p3.Y);
        }
        else
        {
            _X = p1.X;
            _Y = p1.Y;
            _Right = p3.X;
            _Top = p3.Y;
        }
    }
    #endregion

    #region 重载运算符_比较
    public override bool Equals(object? obj)
    {
        return this.Equals(obj as Rect);
    }
    public bool Equals(Rect? b)
    {
        return this.Equals(b, 1e-6);
    }
    public static bool operator !=(Rect? a, Rect? b)
    {
        return !(a == b);
    }
    public static bool operator ==(Rect? a, Rect? b)
    {
        // 此处地方不允许使用==null,因为此处是定义
        if (b is null)
            return a is null;
        else if (a is null)
            return false;
        if (ReferenceEquals(a, b))// 同一对象
            return true;

        return a.Equals(b, 0);
    }

    /// <summary>
    /// 比较核心
    /// </summary>
    public bool Equals(Rect? b, double tolerance = 1e-6)
    {
        if (b is null)
            return false;
        if (ReferenceEquals(this, b)) // 同一对象
            return true;

        return Math.Abs(_X - b._X) < tolerance &&
               Math.Abs(_Right - b._Right) < tolerance &&
               Math.Abs(_Top - b._Top) < tolerance &&
               Math.Abs(_Y - b._Y) < tolerance;
    }

    public override int GetHashCode()
    {
        return (((int)_X ^ (int)_Y).GetHashCode() ^ (int)_Right).GetHashCode() ^ (int)_Top;
    }
    #endregion

    #region 包含
    public bool Contains(PointV PointV)
    {
        return ContainsInternal(PointV.X, PointV.Y);
    }
    public bool Contains(double x, double y)
    {
        return ContainsInternal(x, y);
    }
    private bool ContainsInternal(double x, double y)
    {
        return _X <= x && x <= _Right &&
               _Y <= y && y <= _Top;
    }

    /// <summary>
    /// 四个点都在内部就是包含
    /// </summary>
    /// <param name="rect"></param>
    /// <returns></returns>
    public bool Contains(Rect rect)
    {
        return _X <= rect._X && rect._Right <= _Right &&
               _Y <= rect._Y && rect._Top <= _Top;
    }

    /// <summary>
    /// 一个点在内部就是碰撞
    /// </summary>
    /// <param name="rect"></param>
    /// <returns></returns>
    public bool IntersectsWith(Rect rect)
    {
        return rect._X <= _Right && _X <= rect._Right &&
               rect._Top >= _Y && rect._Y <= _Top;
    }
    #endregion

    #region 方法
    /// <summary>
    /// 获取共点
    /// </summary>
    /// <returns></returns>
    public PointV[] GetCommonPoint(Rect other)
    {
        return ToPoints().Intersect(other.ToPoints(), PointV.Distinct).ToArray();
    }

    public PointV[] ToPoints()
    {
        PointV a = MinPoint;// min
        PointV b = new(_Right, _Y);
        PointV c = MaxPoint;// max
        PointV d = new(_X, _Top);
        return new PointV[] { a, b, c, d };
    }

#if CAD
    public (PointV boxMin, PointV boxRigthDown, PointV boxMax, PointV boxLeftUp) ToPoints4()
    {
        PointV a = MinPoint;// min
        PointV b = new(_Right, _Y);
        PointV c = MaxPoint;// max
        PointV d = new(_X, _Top);
        return (a, b, c, d);
    }
#endif

    /// <summary>
    /// 四周膨胀
    /// </summary>
    /// <returns></returns>
    public Rect Expand(double d)
    {
        return new Rect(_X - d, _Y - d, _Right + d, _Top + d);
    }

    /// <summary>
    /// 是否矩形(带角度)
    /// </summary>
    /// <param name="ptList"></param>
    /// <returns></returns>
    public static bool IsRectAngle(List<PointV>? ptList, double tolerance = 1e-8)
    {
        if (ptList == null)
            throw new ArgumentException(null, nameof(ptList));

        var pts = ptList.ToList();
        /*  消重,不这里设置,否则这不是一个正确的单元测试
         *  // var ptList = pts.Distinct().ToList();
         *  var ptList = pts.DistinctExBy((a, b) => a.DistanceTo(b) < 1e-6).ToList();
         */
        if (ptList.Count == 5)
        {
            // 首尾点相同移除最后
            // if (pts[0].IsEqualTo(pts[^1], tolerance))
            if (pts[0].IsEqualTo(pts[pts.Count - 1], tolerance))
                pts.RemoveAt(pts.Count - 1);
        }
        if (pts.Count != 4)
            return false;

        // 最快的方案
        // 点乘求值法:(为了处理 正梯形/平行四边形 需要三次)
        // 这里的容差要在1e-8内,因为点乘的三次浮点数乘法会令精度变低
        var dot = pts[0].DotProductValue(pts[1], pts[3]);
        if (Math.Abs(dot) < tolerance)
        {
            dot = pts[1].DotProductValue(pts[2], pts[0]);
            if (Math.Abs(dot) < tolerance)
            {
                dot = pts[2].DotProductValue(pts[3], pts[1]);
                return Math.Abs(dot) < tolerance;
            }
        }
        return false;
    }

    /// <summary>
    /// 是否轴向矩形(无角度)
    /// </summary>
    public static bool IsRect(List<PointV>? ptList, double tolerance = 1e-10)
    {
        if (ptList == null)
            throw new ArgumentException(null, nameof(ptList));

        var pts = ptList.ToList();
        if (ptList.Count == 5)
        {
            // 首尾点相同移除最后
            // if (pts[0].IsEqualTo(pts[^1], tolerance))
            if (pts[0].IsEqualTo(pts[pts.Count - 1], tolerance))
                pts.RemoveAt(pts.Count - 1);
        }
        if (pts.Count != 4)
            return false;

        return Math.Abs(pts[0].X - pts[3].X) < tolerance &&
               Math.Abs(pts[0].Y - pts[1].Y) < tolerance &&
               Math.Abs(pts[1].X - pts[2].X) < tolerance &&
               Math.Abs(pts[2].Y - pts[3].Y) < tolerance;
    }

#if CAD
    /// <summary>
    /// 获取点集的包围盒的最小点和最大点(无角度)
    /// </summary>
    /// <param name="pts"></param>
    public static (PointV boxMin, PointV boxMax) GetMinMax(IEnumerable<PointV> pts)
    {
        var xMin = double.MaxValue;
        var xMax = double.MinValue;
        var yMin = double.MaxValue;
        var yMax = double.MinValue;
        var zMin = double.MaxValue;
        var zMax = double.MinValue;

        foreach (var pt in pts)
        {
            xMin = Math.Min(pt.X, xMin);
            xMax = Math.Max(pt.X, xMax);
            yMin = Math.Min(pt.Y, yMin);
            yMax = Math.Max(pt.Y, yMax);
            zMin = Math.Min(pt.Z, zMin);
            zMax = Math.Max(pt.Z, zMax);
        }
        return (new PointV(xMin, yMin, zMin), new PointV(xMax, yMax, zMax));
    }


    /// <summary>
    /// 矩形点序逆时针排列,将min点[0],max点是[3](带角度)
    /// </summary>
    /// <param name="pts"></param>
    /// <returns></returns>
    public static bool RectAnglePointOrder(List<PointV>? pts)
    {
        if (pts == null)
            throw new ArgumentException(null, nameof(pts));

        if (!Rect.IsRectAngle(pts))
            return false;

        var (minPt, _) = PointV.GetMinMax(pts);


        var link = new LoopList<PointV>();
        link.AddRange(pts);

        pts.Clear();
        // 排序这四个点,左下/右下/右上/左上
        var node = link.Find(minPt);
        for (int i = 0; i < 4; i++)
        {
            pts.Add(node!.Value);
            node = node.Next;
        }
        // 保证是逆时针
        var isAcw = pts[0].CrossAclockwise(pts[1], pts[2]);
        if (!isAcw)
        {
#pragma warning disable IDE0180 // 使用元组交换值
            var tmp = pts[1];
            pts[1] = pts[3];
            pts[3] = tmp;
#pragma warning restore IDE0180 // 使用元组交换值
        }
        return true;
    }

    /// <summary>
    /// 生成多段线
    /// </summary>
    /// <returns></returns>
    public Polyline ToPolyLine()
    {
        var pl = new Polyline();
        pl.SetDatabaseDefaults();
        var pts = ToPoints();
        for (int i = 0; i < pts.Length; i++)
            pl.AddVertexAt(i, new Point2d(pts[i]._X, pts[i]._Y), 0, 0, 0);
        return pl;
    }
#endif

    /// <summary>
    /// 列扫碰撞检测(碰撞算法)
    /// 比四叉树还快哦~
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="box">继承Rect的集合</param>
    /// <param name="firstProcessing">先处理集合每一个成员;返回true就跳过后续委托</param>
    /// <param name="collisionProcessing">碰撞,返回两个碰撞的成员;返回true就跳过后续委托</param>
    /// <param name="lastProcessing">后处理集合每一个成员</param>
    public static void XCollision<T>(List<T> box,
        Func<T, bool> firstProcessing,
        Func<T, T, bool> collisionProcessing,
        Action<T> lastProcessing) where T : Rect
    {
        // 先排序X:不需要Y排序,因为Y的上下浮动不共X .ThenBy(a => a.Box.Y)
        // 因为先排序就可以有序遍历x区间,超过就break,达到更快
        box = box.OrderBy(a => a._X).ToList();

        // 遍历所有图元
        for (int i = 0; i < box.Count; i++)
        {
            var oneRect = box[i];
            if (firstProcessing(oneRect))
                continue;

            bool actionlast = true;

            // 搜索范围要在 one 的头尾中间的部分
            for (int j = i + 1; j < box.Count; j++)
            {
                var twoRect = box[j];
                // x碰撞:矩形2的Left 在 矩形1[Left-Right]闭区间
                if (oneRect._X <= twoRect._X && twoRect._X <= oneRect._Right)
                {
                    // y碰撞,那就是真的碰撞了
                    if ((oneRect._Top >= twoRect._Top && twoRect._Top >= oneRect._Y) /*包容上边*/
                     || (oneRect._Top >= twoRect._Y && twoRect._Y >= oneRect._Y)     /*包容下边*/
                     || (twoRect._Top >= oneRect._Top && oneRect._Y >= twoRect._Y))  /*穿过*/
                    {
                        if (collisionProcessing(oneRect, twoRect))
                            actionlast = false;
                    }
                    // 这里想中断y高过它的无意义比较,
                    // 但是必须排序Y,而排序Y必须同X,而这里不是同X(而是同X区间),所以不能中断
                    // 而做到X区间排序,就必须创造一个集合,再排序这个集合,
                    // 会导致每个图元都拥有一次X区间集合,开销更巨大(因此放弃).
                }
                else
                    break;// 因为已经排序了,后续的必然超过 x碰撞区间
            }

            if (actionlast)
                lastProcessing(oneRect);
        }
    }

    #endregion

    #region 转换类型
#if CAD
    // 隐式转换(相当于是重载赋值运算符)
    public static implicit operator Rect(System.Windows.Rect rect)
    {
        return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top);
    }
    public static implicit operator Rect(System.Drawing.RectangleF rect)
    {
        return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top);
    }
    public static implicit operator Rect(System.Drawing.Rectangle rect)
    {
        return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top);
    }
#endif
    #region ToString
    public override string ToString()
    {
        return ToString(null, null);
    }
    public string ToString(IFormatProvider? provider)
    {
        return ToString(null, provider);
    }
    public string ToString(string? format = null, IFormatProvider? formatProvider = null)
    {
        return $"({_X.ToString(format, formatProvider)},{_Y.ToString(format, formatProvider)})," +
               $"({_Right.ToString(format, formatProvider)},{_Top.ToString(format, formatProvider)})";
    }

    public Rect Clone()
    {
        return new Rect(Left, Top, Right, Bottom);
    }
    #endregion
    #endregion
}